﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using Properties;

namespace Borg
{
    public class PlugInfo : RepositoryElement
    {
        internal static int nextId = 1;
        internal readonly PlugTypeInfo plugTypeInfo;
        internal readonly ExtensionInfo extensionInfo;
        internal RepositoryCollection<SlotInfo> pluggedInSlots;
        internal bool autoPlug;

        public event CancelPlugEventHandler Plugging;
        public event PlugEventHandler Plugged;
        public event CancelPlugEventHandler Unplugging;
        public event PlugEventHandler Unplugged;

        public override string Name
        {
            get { return plugTypeInfo.Name; }
        }

        public override Assembly Assembly
        {
            get { return extensionInfo.extensionTypeInfo.pluginInfo.assembly; }
        }

        public ExtensionInfo ExtensionInfo
        {
            get { return extensionInfo; }
        }

        public object Object
        {
            get { return extensionInfo.extensionObject; }
        }

        public RepositoryCollection<SlotInfo> PluggedInSlots
        {
            get { return pluggedInSlots; }
        }

        public PlugTypeInfo PlugTypeInfo
        {
            get { return plugTypeInfo; }
        }

        public bool AutoPlug
        {
            get { return autoPlug; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetAutoPlug(this, value);
            }
        }

        public SlotDefinition SlotDefinition
        {
            get { return plugTypeInfo.SlotDefinition; }
        }

        static PlugInfo()
        { }

        internal PlugInfo(PlugTypeInfo plugTypeInfo, ExtensionInfo extensionInfo)
        {
            id = nextId++;

            this.plugTypeInfo = plugTypeInfo;
            this.extensionInfo = extensionInfo;

            pluggedInSlots = new RepositoryCollection<SlotInfo>(new List<SlotInfo>());
            autoPlug = plugTypeInfo.AutoPlug;

            if (!plugTypeInfo.eventHandlerMethodInfosSet)
                return;

            if (plugTypeInfo.MiOnPlugging != null)
                Plugging += new ReflectionEventHandler(extensionInfo.Object, plugTypeInfo.MiOnPlugging).InvokeMethod;

            if (plugTypeInfo.MiOnPlugged != null)
                Plugged += new ReflectionEventHandler(extensionInfo.Object, plugTypeInfo.MiOnPlugged).InvokeMethod;

            if (plugTypeInfo.MiOnUnplugging != null)
                Unplugging += new ReflectionEventHandler(extensionInfo.Object, plugTypeInfo.MiOnUnplugging).InvokeMethod;

            if (plugTypeInfo.MiOnUnplugged != null)
                Unplugged += new ReflectionEventHandler(extensionInfo.Object, plugTypeInfo.MiOnUnplugged).InvokeMethod;
        }

        public T GetObject<T>()
        {
            return (T)Object;
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Plug()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Plug(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Plug(SlotInfo si)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Plug(this, si);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Unplug()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Unplug(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Unplug(SlotInfo si)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Unplug(this, si);
        }

        internal void InternalPlug(uint taskGroupId, Assembly initiator)
        {
            var status = false;

            foreach (var slotInfo in plugTypeInfo.registeredInSlots)
                if (!slotInfo.pluggedPlugInfos.Contains(this) &&
                    slotInfo.InternalPlug(this, true, taskGroupId, initiator, false))
                    status = true;

            if (!status)
                return;

            Runtime.TaskQueue.Enqueue(
                TaskFactory.CreateOpenSlots(
                    extensionInfo,
                    false,
                    taskGroupId,
                    Runtime.Assembly));
        }

        internal void InternalUnplug(Assembly initiator, bool force)
        {
            for (var i = pluggedInSlots.Count - 1; i >= 0; --i)
                pluggedInSlots[i].InternalUnplug(this, initiator, force);
        }

        internal void OnPlugging(object sender, CancelPlugEventArgs args)
        {
            if (!plugTypeInfo.eventHandlerMethodInfosSet)
                plugTypeInfo.VerifyEventHandlerMethods(false);

            if (Plugging != null)
                Runtime.InvokeCancelEventHandler(
                    string.Format(
                        Messages.PlugEventHandlerMethodDesc,
                        "Plugging",
                        Name),
                    Plugging,
                    this,
                    args);

            plugTypeInfo.OnPlugging(this, args);
        }

        internal void OnPlugged(object sender, PlugEventArgs args)
        {
            if (Plugged != null)
                Runtime.InvokeEventHandler(
                    string.Format(
                        Messages.PlugEventHandlerMethodDesc,
                        "Plugged",
                        Name),
                    Plugged,
                    sender,
                    args);

            plugTypeInfo.OnPlugged(this, args);
        }

        internal void OnUnplugging(object sender, CancelPlugEventArgs args)
        {
            if (Unplugging != null)
                Runtime.InvokeCancelEventHandler(
                    string.Format(
                        Messages.PlugEventHandlerMethodDesc,
                        "Unplugging",
                        Name),
                    Unplugging,
                    sender,
                    args);

            plugTypeInfo.OnUnplugging(this, args);
        }

        internal void OnUnplugged(object sender, PlugEventArgs args)
        {
            if (Unplugged != null)
                Runtime.InvokeEventHandler(
                    string.Format(
                        Messages.PlugEventHandlerMethodDesc,
                        "Unplugged",
                        Name),
                    Unplugged,
                    sender,
                    args);

            plugTypeInfo.OnUnplugged(this, args);
        }
    }
}

